package domain.tc;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import tableaux.ClashTrigger;
import utils.exception.ConcreteDomainException;

import entities.axiom.AxiomType;
import entities.concept.Concept;
//import entities.concept.DomainConceptEQ;
//import entities.concept.DomainConceptLT;
import entities.role.Role;

public class TCNode {



	protected boolean selfloop = false;
	public TCNode root = null;
	public Component scc = null;
	protected int hsaved = 0;

	private Object obj; // which uniquely identifies a node, either a feature or
						// a constant
	private Set<TCNode> ltEdges;
	private Set<TCNode> lteqEdges;
	private Set<TCNode> eqEdges;
	private static Map<Object, TCNode> AllNodes = new HashMap<Object, TCNode>();

	public static enum EdgeType {
		LT, LT_EQ, EQ
	}

	public Set<TCNode> getLTEdges() {
		return this.ltEdges;
	}

	public Set<TCNode> getEQEdges() {
		return this.eqEdges;
	}

	public static void clearAllNodes() {
		AllNodes.clear();
	}

	public static int getGraphNodeSize() {
		return AllNodes.size();
	}

	public static Collection<TCNode> getAllNodes() {
		return AllNodes.values();
	}

	// might be buggy to use Set here
	public Set<TCNode> getEdges() {
		Set<TCNode> ret = new HashSet<TCNode>();
		ret.addAll(ltEdges);
		ret.addAll(lteqEdges);
		ret.addAll(eqEdges);
		return ret;
	}

	// test if this node -> w is a LT edge
	public boolean isBadEdge(TCNode w) {
		return this.ltEdges.contains(w);
	}

	private TCNode(Object obj) {
		this.obj = obj;
		this.eqEdges = new HashSet<TCNode>();
		this.ltEdges = new HashSet<TCNode>();
		this.lteqEdges = new HashSet<TCNode>();
	}

	public static TCNode getInstance(Object obj) throws ConcreteDomainException {
		if (!(obj instanceof Role) && !(obj instanceof String)) {
			throw new ConcreteDomainException("Object incompatible for a node.");
		}
		if (!AllNodes.containsKey(obj)) {
			TCNode nt = new TCNode(obj);
			AllNodes.put(obj, nt);
			return nt;
		}
		return AllNodes.get(obj);
	}

	public static TCNode getConceptAsNode(Concept c) {
		if (!AllNodes.containsKey(c)) {
			TCNode nt = new TCNode(c);
			AllNodes.put(c, nt);
			return nt;
		}
		return AllNodes.get(c);
	}

	public boolean addEdge(EdgeType op, Object fs) {
		TCNode v = null;
		try {
			v = TCNode.getInstance(fs);
		} catch (ConcreteDomainException e) {
			e.printStackTrace();
		}
		// System.out.println("Add edge: "+this.toString()+" *"+op.name()+"* "+fs);
		return this.addEdge(op, v);
	}

	// two types of axioms, either = or <
	public void addAxiomEdge(AxiomType op, TCNode v) {
		if (op.equals(AxiomType.EQUIVALENCE_AXIOM)) {
			this.eqEdges.add(v);
			// if f=g, then g=f
			try {
				if (!v.eqEdges.contains(this)) {
					v.addAxiomEdge(op, this);
				}
			} catch (Exception e) {
				System.err.println(this.toString() + " -> " + v.toString());
			}
		} else if (op.equals(AxiomType.INCLUSION_AXIOM)) {
			this.ltEdges.add(v);
		} else {
			System.err.println("Unknown axiom type: " + op.name());
		}
	}

	// find initial clash, e.g., f=3 and f=4, f=2 and f<=1, f=3 and f<3, etc.
	// conflict between < and =; no effect in the case of <=
	// return true if clash found
	public boolean addEdge(EdgeType op, TCNode v) {
		if (op.equals(EdgeType.EQ)) {
			this.eqEdges.add(v);
			boolean rev = true;
			// if f=g, then g=f
			try {
				if (!v.eqEdges.contains(this)) {
					rev = v.addEdge(op, this);
				}
			} catch (Exception e) {
				System.out.println(this.toString() + " -> " + v.toString());
			}
			return rev && this.ltEdges.contains(v);
		} else if (op.equals(EdgeType.LT)) {
			this.ltEdges.add(v);
			boolean clash = false;
			clash |= this.eqEdges.contains(v);
			if (clash) {
				// this.addClashCps(new DomainConceptLT<Object,Object>
				// (this,v));
				// System.err.println("Clashes: "+this+"; "+v);
				// //add both this=v and v=this
				// this.addClashCps(new DomainConceptEQ<Object,Object>
				// (this,v));
				// this.addClashCps(new DomainConceptEQ<Object,Object>
				// (v,this));
				return clash;
			}
			// clash if f<g and g<f
			clash |= v.ltEdges.contains(this);
			if (clash) {
				// this.addClashCps(new DomainConceptLT<Object,Object>
				// (this,v));
				// //add v<this
				// this.addClashCps(new DomainConceptLT<Object,Object>
				// (v,this));
				return clash;
			}
			return clash;
		} else if (op.equals(EdgeType.LT_EQ)) {
			this.lteqEdges.add(v);
		} else {
			System.err.println("Unknown edge type: " + op.name());
		}
		return false;
	}

	public boolean hasChild(TCNode u) {
		return this.ltEdges.contains(u) || this.eqEdges.contains(u)
				|| this.lteqEdges.contains(u);
	}

	// return if u is reachable from this node
	public boolean hasDescendant(TCNode u) {
		return this.DFS(this, new ArrayList<TCNode>()).contains(u);
	}

	// get all the successors of a node, using DFS
	public List<TCNode> DFS(TCNode root, List<TCNode> given) {
		given.add(root);
		for (TCNode v : root.getEdges()) {
			if (!given.contains(v)) {
				given = DFS(v, given);
			}
		}
		return given;
	}

	// return the list of nodes of a strongly connected component
	public List<TCNode> getComponentContent() {
		if (this.scc != null) {
			return this.scc.content;
		}
		return null;
	}

	private volatile int hashCode = 0;
	@Override
	public int hashCode() {
		if (hashCode != 0) {
			return hashCode;
		}
		final int prime = 31;
		int result = 1;
		result = prime * result + ((obj == null) ? 0 : obj.hashCode());
		hashCode = result;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof TCNode))
			return false;
		TCNode other = (TCNode) obj;
		if (this.obj == null) {
			if (other.obj != null)
				return false;
		} else if (!this.obj.equals(other.obj))
			return false;
		return true;
	}
	
	@Override
	public String toString() {
		if (this.obj instanceof Role) {
			return ((Role) this.obj).toSerializedString();
		} else if (this.obj instanceof String) {
			return this.obj.toString();
		} else if (this.obj instanceof Concept) {
			return ((Concept) this.obj).toSerializedString();
		}
		return "";
	}

	public void addClashCps(Concept c) {
		InitDomainGraph g = InitGraphAccessor.getDefault().currentInitGraph();
		ClashTrigger ct = g.getAssociatedClashTrigger();
		ct.addClashCp(c);
	}

}

class Component {
	public List<TCNode> content = new ArrayList<TCNode>();
	public Set<Component> successors = new HashSet<Component>();
	public int sort = -1;
	
//	private volatile int hashCode = 0;
	@Override
	public int hashCode() {
//		if (hashCode != 0) {
//			return hashCode;
//		}
		final int prime = 31;
		int result = 1;
		result = prime * result + ((content == null) ? 0 : content.hashCode());
//		hashCode = result;
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof Component))
			return false;
		Component other = (Component) obj;
		if (content == null) {
			if (other.content != null)
				return false;
		} else if (!content.equals(other.content))
			return false;
		return true;
	}
	
}